﻿using System;
using Word = Microsoft.Office.Interop.Word;

namespace ExcelToWord.Common {
    internal class WordDocument : IDisposable{

        private Word.Application app = new Microsoft.Office.Interop.Word.Application();
        private object oEndOfDoc = "\\endofdoc";
        private object missing = System.Reflection.Missing.Value;
        private Word.Document doc;

        internal static WordDocument Create() {
            return new WordDocument();
        }

        internal WordDocument() {
            doc = null;
        }

        /// <summary>
        /// Create a document
        /// </summary>
        /// <param name="visible">indicate whether the document would be shown</param>
        internal void NewDocument(bool visible) {
            if (doc != null) {
                throw new InvalidOperationException("doc exists");
            }
            app.Visible = visible;
            object v = visible;
            doc = app.Documents.Add(ref missing, ref missing, ref missing, ref v);
        }

        /// <summary>
        /// Open a exists document
        /// </summary>
        /// <param name="filename">filename</param>
        /// <param name="visible">indicate whether the document would be shown</param>
        internal void OpenDocument(string filename, bool visible) {
            if (doc != null) {
                throw new InvalidOperationException("doc exists");
            }
            app.Visible = visible;
            object fn = filename;
            object v = visible;
            doc = app.Documents.Open(ref fn, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing,
                                    ref missing, ref missing);

        }

        internal Word.Range AddXmlLast(string text) {
            Word.Range range = doc.Bookmarks.get_Item(ref oEndOfDoc).Range;
            range.InsertXML(text, ref missing);
            return range;
        }

        /// <summary>
        /// Copy  whole document to the ClipBoard
        /// </summary>
        internal void CopyWholeDoc() {
            doc.Content.WholeStory();
            doc.Content.Copy();
        }

        internal string CopyWholeXml() {
            return doc.Content.get_XML(false);
        }

        /// <summary>
        /// Paste the content from ClipBoard to the end of document;
        /// </summary>
        /// <returns></returns>
        internal Word.Range PasteLast() {

            if (doc == null) {
                throw new NullReferenceException("doc");
            }

            Word.Range range = doc.Bookmarks.get_Item(ref oEndOfDoc).Range;
            range.Paste();
            return range;
        }

        /// <summary>

        /// Find the passed in text in the document.

        /// </summary>

        /// <param name="startOffset">Start the find at this offset.</param>

        /// <param name="endOffset">End the find at this offset.</param>

        /// <param name="forward">true if search forward.</param>

        /// <param name="text">the text to find.</param>

        /// <returns>The range the text is at, null if not found.</returns>
        private Word.Range Find(int startOffset, int endOffset, bool forward, string text) {



            // Word sometimes won't find it if it starts at startOffset. So we start 1 earlier and if it's

            // there also, we error out on the find and do it the hard way below.

            int rangeStart = Math.Max(0, startOffset - 1);

            // Word API has the end as exclusive, but we treat it as inclusive

            int rangeEnd = Math.Min(endOffset + 1, doc.Content.End);



            object start = rangeStart;

            object end = rangeEnd;

            Word.Range range = doc.Range(ref start, ref end);

            range.Find.ClearFormatting();

            range.Find.Forward = forward;

            range.Find.Text = text;

            range.Find.Wrap = Word.WdFindWrap.wdFindStop;


            object missingValue = Type.Missing;

            range.Find.Execute(ref missingValue, ref missingValue,

                ref missingValue, ref missingValue, ref missingValue,

                ref missingValue, ref missingValue, ref missingValue,

                ref missingValue, ref missingValue, ref missingValue,

                ref missingValue, ref missingValue, ref missingValue,

                ref missingValue);



            // if in our range, we have it

            if (range.Find.Found && (startOffset <= range.Start) && (range.End <= rangeEnd))

                return range;



            // there is a bug where it sometimes doesn't find text. So here we do it the old fashioned way.

            // find the start - there may be several tags before the tag we are on in this para.

            // and we might be going forward - or backwards!

            start = startOffset;

            end = rangeEnd;

            range = doc.Range(ref start, ref end);

            string docText = range.Text;

            if (docText == null)

                return null;

            int ind = forward ? docText.IndexOf(text) : docText.LastIndexOf(text);

            if (ind == -1)

                return null;



            // ok, figure out the range for this - can't jump to start + ind as that can go past the begining (no idea why)

            int _start = forward ? startOffset : rangeEnd - text.Length;

            while ((startOffset <= _start) && (_start < rangeEnd)) {

                start = _start;

                end = rangeEnd;

                range = doc.Range(ref start, ref end);

                docText = range.Text;



                // see if we are now starting on it - make sure it's that tag in our range

                if ((docText != null) && docText.StartsWith(text)) {

                    // get all of it (may have formatting in it)

                    int endPos = range.Start + text.Length;

                    while (endPos < endOffset) {

                        range.End = endPos;

                        docText = range.Text;

                        if (docText == text)

                            break;

                        endPos++;

                    }

                    return range;

                }

                _start += forward ? 1 : -1;

            }

            return null;

        }

        /// <summary>
        /// Replace Text
        /// </summary>
        /// <param name="findText"></param>
        /// <param name="replaceWithText"></param>
        /// <param name="matchCase">not used</param>
        /// <returns></returns>
        internal bool Replace(string findText, string replaceWithText, bool matchCase) {

            return Replace(doc.Content, findText, replaceWithText, matchCase);

        }

        /// <summary>
        /// Replace Text
        /// </summary>
        /// <param name="range">search range</param>
        /// <param name="findText"></param>
        /// <param name="replaceWithText"></param>
        /// <param name="matchCase">not used</param>
        /// <returns>if replaced</returns>
        internal bool Replace(Word.Range range, string findText, 
            string replaceWithText, bool matchCase) {

            if (range == null) {
                throw new NullReferenceException("range");
            }
            //doc.Content.Find.ClearFormatting();
            //object _findText = findText;
            //object _replaceWithText = replaceWithText.Replace("\n","^p");
            //object _matchCase = matchCase;
            //object _matchWholeWord = true;
            //object matchWildCards = false;
            //object matchSoundsLike = false;
            //object matchAllWordForms = false;
            //object forward = true;
            //object format = false;
            //object matchKashida = false;
            //object matchDiacritics = false;
            //object matchAlefHamza = false;
            //object matchControl = false;
            //object readOnly = false;
            //object visible = false;
            //object replace = Word.WdReplace.wdReplaceNone;
            //object wrap = 1;

            //bool found =  range.Find.Execute(ref _findText,
            //    ref matchCase, ref _matchWholeWord,
            //    ref matchWildCards, ref matchSoundsLike,
            //    ref matchAllWordForms, ref forward,
            //    ref wrap, ref format, ref _replaceWithText,
            //    ref replace, ref matchKashida,
            //    ref matchDiacritics, ref matchAlefHamza,
            //    ref matchControl);
            //range.Application.Options.ReplaceSelection = true;
            //bool found = true;
            //while (found) {
            //    Console.WriteLine(findText + "," + replaceWithText);
            //    found = doc.Application.Selection.Find.Execute(ref _findText,
            //        ref matchCase, ref _matchWholeWord,
            //        ref matchWildCards, ref matchSoundsLike,
            //        ref matchAllWordForms, ref forward,
            //        ref wrap, ref format, ref missing,
            //        ref replace, ref matchKashida,
            //        ref matchDiacritics, ref matchAlefHamza,
            //        ref matchControl);
            //    if (found) {
            //        range.Application.Selection.TypeText(replaceWithText);
            //        range
            //    }
            //} 

            Word.Range foundRange = range;

            bool found = false;

            while (foundRange != null) {

                foundRange = this.Find(range.Start, range.End, true, findText);

                if (foundRange != null) {

                    found = true;

                    foundRange.Text = replaceWithText.ToString();

                }
            }

            return found;
        }

        internal void Save() {
            doc.Save();
        }

        internal void SaveAs(string filename) {
            object fn = filename;
            doc.SaveAs(ref fn, ref missing, 
                ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, 
                ref missing, ref missing, ref missing, ref missing, 
                ref missing, ref missing);
        }


        #region IDisposable 成员

        public void Dispose() {
            try {
                ((Microsoft.Office.Interop.Word._Document)doc).Close(ref missing, ref missing, ref missing);
            } catch {

            } finally {
                doc = null;
            }

            try {
                ((Microsoft.Office.Interop.Word._Application)app).Quit(ref missing, ref missing, ref missing);
            } catch {

            } finally {
                app = null;
            }
        }

        #endregion
    }
}
